package org.jcodec.codecs.h264.decode;

import static org.jcodec.common.tools.MathUtil.clip;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 * 
 * Builds intra prediction for intra 4x4 coded macroblocks
 * 
 * @author The JCodec project
 * 
 */
public class Intra4x4PredictionBuilder {

    public static void predictWithMode(int mode, int[] residual, boolean leftAvailable, boolean topAvailable,
            boolean topRightAvailable, byte[] leftRow, byte[] topLine, byte topLeft[], int mbOffX, int blkX, int blkY,
            byte[] pixOut) {
        switch (mode) {
        case 0:
            predictVertical(residual, topAvailable, topLine, mbOffX, blkX, blkY, pixOut);
            break;
        case 1:
            predictHorizontal(residual, leftAvailable, leftRow, mbOffX, blkX, blkY, pixOut);
            break;
        case 2:
            predictDC(residual, leftAvailable, topAvailable, leftRow, topLine, mbOffX, blkX, blkY, pixOut);
            break;
        case 3:
            predictDiagonalDownLeft(residual, topAvailable, topRightAvailable, topLine, mbOffX, blkX, blkY, pixOut);
            break;
        case 4:
            predictDiagonalDownRight(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, mbOffX, blkX,
                    blkY, pixOut);
            break;
        case 5:
            predictVerticalRight(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, mbOffX, blkX, blkY,
                    pixOut);
            break;
        case 6:
            predictHorizontalDown(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, mbOffX, blkX, blkY,
                    pixOut);
            break;
        case 7:
            predictVerticalLeft(residual, topAvailable, topRightAvailable, topLine, mbOffX, blkX, blkY, pixOut);
            break;
        case 8:
            predictHorizontalUp(residual, leftAvailable, leftRow, mbOffX, blkX, blkY, pixOut);
            break;
        }

        int oo1 = mbOffX + blkX;
        int off1 = (blkY << 4) + blkX + 3;

        topLeft[blkY >> 2] = topLine[oo1 + 3];

        leftRow[blkY] = pixOut[off1];
        leftRow[blkY + 1] = pixOut[off1 + 16];
        leftRow[blkY + 2] = pixOut[off1 + 32];
        leftRow[blkY + 3] = pixOut[off1 + 48];

        int off2 = (blkY << 4) + blkX + 48;
        topLine[oo1] = pixOut[off2];
        topLine[oo1 + 1] = pixOut[off2 + 1];
        topLine[oo1 + 2] = pixOut[off2 + 2];
        topLine[oo1 + 3] = pixOut[off2 + 3];
    }

    public static void predictVertical(int[] residual, boolean topAvailable, byte[] topLine, int mbOffX, int blkX,
            int blkY, byte[] pixOut) {

        int pixOff = (blkY << 4) + blkX;
        int toff = mbOffX + blkX;
        int rOff = 0;
        for (int j = 0; j < 4; ++j) {
            pixOut[pixOff] = (byte) clip(residual[rOff] + topLine[toff], -128, 127);
            pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + topLine[toff + 1], -128, 127);
            pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + topLine[toff + 2], -128, 127);
            pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + topLine[toff + 3], -128, 127);
            rOff += 4;
            pixOff += 16;
        }
    }

    public static void predictHorizontal(int[] residual, boolean leftAvailable, byte[] leftRow, int mbOffX, int blkX,
            int blkY, byte[] pixOut) {

        int pixOff = (blkY << 4) + blkX;
        int rOff = 0;
        for (int j = 0; j < 4; j++) {
            int l = leftRow[blkY + j];
            pixOut[pixOff] = (byte) clip(residual[rOff] + l, -128, 127);
            pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + l, -128, 127);
            pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + l, -128, 127);
            pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + l, -128, 127);
            rOff += 4;
            pixOff += 16;
        }
    }

    public static void predictDC(int[] residual, boolean leftAvailable, boolean topAvailable, byte[] leftRow,
            byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) {

        int val;
        if (leftAvailable && topAvailable) {
            val = (leftRow[blkY] + leftRow[blkY + 1] + leftRow[blkY + 2] + leftRow[blkY + 3] + topLine[mbOffX + blkX]
                    + topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + topLine[mbOffX + blkX + 3] + 4) >> 3;
        } else if (leftAvailable) {
            val = (leftRow[blkY] + leftRow[blkY + 1] + leftRow[blkY + 2] + leftRow[blkY + 3] + 2) >> 2;
        } else if (topAvailable) {
            val = (topLine[mbOffX + blkX] + topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2]
                    + topLine[mbOffX + blkX + 3] + 2) >> 2;
        } else {
            val = 0;
        }

        int pixOff = (blkY << 4) + blkX;
        int rOff = 0;
        for (int j = 0; j < 4; j++) {
            pixOut[pixOff] = (byte) clip(residual[rOff] + val, -128, 127);
            pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + val, -128, 127);
            pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + val, -128, 127);
            pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + val, -128, 127);
            pixOff += 16;
            rOff += 4;
        }
    }

    public static void predictDiagonalDownLeft(int[] residual, boolean topAvailable, boolean topRightAvailable,
            byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) {

        int to = mbOffX + blkX;
        int tr0 = topLine[to + 3], tr1 = topLine[to + 3], tr2 = topLine[to + 3], tr3 = topLine[to + 3];
        if (topRightAvailable) {
            tr0 = topLine[to + 4];
            tr1 = topLine[to + 5];
            tr2 = topLine[to + 6];
            tr3 = topLine[to + 7];
        }

        int c0 = ((topLine[to] + topLine[to + 2] + (topLine[to + 1] << 1) + 2) >> 2);
        int c1 = ((topLine[to + 1] + topLine[to + 3] + (topLine[to + 2] << 1) + 2) >> 2);
        int c2 = ((topLine[to + 2] + tr0 + (topLine[to + 3] << 1) + 2) >> 2);
        int c3 = ((topLine[to + 3] + tr1 + (tr0 << 1) + 2) >> 2);
        int c4 = ((tr0 + tr2 + (tr1 << 1) + 2) >> 2);
        int c5 = ((tr1 + tr3 + (tr2 << 1) + 2) >> 2);
        int c6 = ((tr2 + 3 * (tr3) + 2) >> 2);

        int off = (blkY << 4) + blkX;
        pixOut[off] = (byte) clip(residual[0] + c0, -128, 127);
        pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127);
        pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127);
        pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127);

        pixOut[off + 16] = (byte) clip(residual[4] + c1, -128, 127);
        pixOut[off + 17] = (byte) clip(residual[5] + c2, -128, 127);
        pixOut[off + 18] = (byte) clip(residual[6] + c3, -128, 127);
        pixOut[off + 19] = (byte) clip(residual[7] + c4, -128, 127);

        pixOut[off + 32] = (byte) clip(residual[8] + c2, -128, 127);
        pixOut[off + 33] = (byte) clip(residual[9] + c3, -128, 127);
        pixOut[off + 34] = (byte) clip(residual[10] + c4, -128, 127);
        pixOut[off + 35] = (byte) clip(residual[11] + c5, -128, 127);

        pixOut[off + 48] = (byte) clip(residual[12] + c3, -128, 127);
        pixOut[off + 49] = (byte) clip(residual[13] + c4, -128, 127);
        pixOut[off + 50] = (byte) clip(residual[14] + c5, -128, 127);
        pixOut[off + 51] = (byte) clip(residual[15] + c6, -128, 127);
    }

    public static void predictDiagonalDownRight(int[] residual, boolean leftAvailable, boolean topAvailable,
            byte[] leftRow, byte[] topLine, byte[] topLeft, int mbOffX, int blkX, int blkY, byte[] pixOut) {

        int off = (blkY << 4) + blkX;
        int c0 = ((topLine[mbOffX + blkX] + 2 * topLeft[blkY >> 2] + leftRow[blkY] + 2) >> 2);

        int c1 = ((topLeft[blkY >> 2] + (topLine[mbOffX + blkX + 0] << 1) + topLine[mbOffX + blkX + 1] + 2) >> 2);
        int c2 = ((topLine[mbOffX + blkX] + (topLine[mbOffX + blkX + 1] << 1) + topLine[mbOffX + blkX + 2] + 2) >> 2);
        int c3 = ((topLine[mbOffX + blkX + 1] + (topLine[mbOffX + blkX + 2] << 1) + topLine[mbOffX + blkX + 3] + 2) >> 2);

        pixOut[off] = (byte) clip(residual[0] + c0, -128, 127);
        pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127);
        pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127);
        pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127);

        int c4 = ((topLeft[blkY >> 2] + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2);
        int c6 = ((topLeft[blkY >> 2] + (topLine[mbOffX + blkX] << 1) + topLine[mbOffX + blkX + 1] + 2) >> 2);
        int c7 = ((topLine[mbOffX + blkX] + (topLine[mbOffX + blkX + 1] << 1) + topLine[mbOffX + blkX + 2] + 2) >> 2);

        pixOut[off + 16] = (byte) clip(residual[4] + c4, -128, 127);
        pixOut[off + 17] = (byte) clip(residual[5] + c0, -128, 127);
        pixOut[off + 18] = (byte) clip(residual[6] + c6, -128, 127);
        pixOut[off + 19] = (byte) clip(residual[7] + c7, -128, 127);

        int c8 = ((leftRow[blkY + 0] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2);
        int c9 = ((topLeft[blkY >> 2] + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2);
        int c11 = ((topLeft[blkY >> 2] + (topLine[mbOffX + blkX] << 1) + topLine[mbOffX + blkX + 1] + 2) >> 2);

        pixOut[off + 32] = (byte) clip(residual[8] + c8, -128, 127);
        pixOut[off + 33] = (byte) clip(residual[9] + c9, -128, 127);
        pixOut[off + 34] = (byte) clip(residual[10] + c0, -128, 127);
        pixOut[off + 35] = (byte) clip(residual[11] + c11, -128, 127);

        int c12 = ((leftRow[blkY + 1] + (leftRow[blkY + 2] << 1) + leftRow[blkY + 3] + 2) >> 2);
        int c13 = ((leftRow[blkY] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2);
        int c14 = ((topLeft[blkY >> 2] + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2);

        pixOut[off + 48] = (byte) clip(residual[12] + c12, -128, 127);
        pixOut[off + 49] = (byte) clip(residual[13] + c13, -128, 127);
        pixOut[off + 50] = (byte) clip(residual[14] + c14, -128, 127);
        pixOut[off + 51] = (byte) clip(residual[15] + c0, -128, 127);
    }

    public static void predictVerticalRight(int[] residual, boolean leftAvailable, boolean topAvailable,
            byte[] leftRow, byte[] topLine, byte[] topLeft, int mbOffX, int blkX, int blkY, byte[] pixOut) {

        int v1 = (topLeft[blkY >> 2] + topLine[mbOffX + blkX + 0] + 1) >> 1;
        int v2 = (topLine[mbOffX + blkX + 0] + topLine[mbOffX + blkX + 1] + 1) >> 1;
        int v3 = (topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + 1) >> 1;
        int v4 = (topLine[mbOffX + blkX + 2] + topLine[mbOffX + blkX + 3] + 1) >> 1;
        int v5 = (leftRow[blkY] + 2 * topLeft[blkY >> 2] + topLine[mbOffX + blkX + 0] + 2) >> 2;
        int v6 = (topLeft[blkY >> 2] + 2 * topLine[mbOffX + blkX + 0] + topLine[mbOffX + blkX + 1] + 2) >> 2;
        int v7 = (topLine[mbOffX + blkX + 0] + 2 * topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + 2) >> 2;
        int v8 = (topLine[mbOffX + blkX + 1] + 2 * topLine[mbOffX + blkX + 2] + topLine[mbOffX + blkX + 3] + 2) >> 2;
        int v9 = (topLeft[blkY >> 2] + 2 * leftRow[blkY] + leftRow[blkY + 1] + 2) >> 2;
        int v10 = (leftRow[blkY] + 2 * leftRow[blkY + 1] + leftRow[blkY + 2] + 2) >> 2;

        int off = (blkY << 4) + blkX;
        pixOut[off] = (byte) clip(residual[0] + v1, -128, 127);
        pixOut[off + 1] = (byte) clip(residual[1] + v2, -128, 127);
        pixOut[off + 2] = (byte) clip(residual[2] + v3, -128, 127);
        pixOut[off + 3] = (byte) clip(residual[3] + v4, -128, 127);
        pixOut[off + 16] = (byte) clip(residual[4] + v5, -128, 127);
        pixOut[off + 17] = (byte) clip(residual[5] + v6, -128, 127);
        pixOut[off + 18] = (byte) clip(residual[6] + v7, -128, 127);
        pixOut[off + 19] = (byte) clip(residual[7] + v8, -128, 127);
        pixOut[off + 32] = (byte) clip(residual[8] + v9, -128, 127);
        pixOut[off + 33] = (byte) clip(residual[9] + v1, -128, 127);
        pixOut[off + 34] = (byte) clip(residual[10] + v2, -128, 127);
        pixOut[off + 35] = (byte) clip(residual[11] + v3, -128, 127);
        pixOut[off + 48] = (byte) clip(residual[12] + v10, -128, 127);
        pixOut[off + 49] = (byte) clip(residual[13] + v5, -128, 127);
        pixOut[off + 50] = (byte) clip(residual[14] + v6, -128, 127);
        pixOut[off + 51] = (byte) clip(residual[15] + v7, -128, 127);
    }

    public static void predictHorizontalDown(int[] residual, boolean leftAvailable, boolean topAvailable,
            byte[] leftRow, byte[] topLine, byte[] topLeft, int mbOffX, int blkX, int blkY, byte[] pixOut) {

        int c0 = (topLeft[blkY >> 2] + leftRow[blkY] + 1) >> 1;
        int c1 = (leftRow[blkY] + 2 * topLeft[blkY >> 2] + topLine[mbOffX + blkX + 0] + 2) >> 2;
        int c2 = (topLeft[blkY >> 2] + 2 * topLine[mbOffX + blkX + 0] + topLine[mbOffX + blkX + 1] + 2) >> 2;
        int c3 = (topLine[mbOffX + blkX + 0] + 2 * topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + 2) >> 2;
        int c4 = (leftRow[blkY] + leftRow[blkY + 1] + 1) >> 1;
        int c5 = (topLeft[blkY >> 2] + 2 * leftRow[blkY] + leftRow[blkY + 1] + 2) >> 2;
        int c6 = (leftRow[blkY + 1] + leftRow[blkY + 2] + 1) >> 1;
        int c7 = (leftRow[blkY] + 2 * leftRow[blkY + 1] + leftRow[blkY + 2] + 2) >> 2;
        int c8 = (leftRow[blkY + 2] + leftRow[blkY + 3] + 1) >> 1;
        int c9 = (leftRow[blkY + 1] + 2 * leftRow[blkY + 2] + leftRow[blkY + 3] + 2) >> 2;

        int off = (blkY << 4) + blkX;
        pixOut[off] = (byte) clip(residual[0] + c0, -128, 127);
        pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127);
        pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127);
        pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127);
        pixOut[off + 16] = (byte) clip(residual[4] + c4, -128, 127);
        pixOut[off + 17] = (byte) clip(residual[5] + c5, -128, 127);
        pixOut[off + 18] = (byte) clip(residual[6] + c0, -128, 127);
        pixOut[off + 19] = (byte) clip(residual[7] + c1, -128, 127);
        pixOut[off + 32] = (byte) clip(residual[8] + c6, -128, 127);
        pixOut[off + 33] = (byte) clip(residual[9] + c7, -128, 127);
        pixOut[off + 34] = (byte) clip(residual[10] + c4, -128, 127);
        pixOut[off + 35] = (byte) clip(residual[11] + c5, -128, 127);
        pixOut[off + 48] = (byte) clip(residual[12] + c8, -128, 127);
        pixOut[off + 49] = (byte) clip(residual[13] + c9, -128, 127);
        pixOut[off + 50] = (byte) clip(residual[14] + c6, -128, 127);
        pixOut[off + 51] = (byte) clip(residual[15] + c7, -128, 127);
    }

    public static void predictVerticalLeft(int[] residual, boolean topAvailable, boolean topRightAvailable,
            byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) {

        int to = mbOffX + blkX;
        int tr0 = topLine[to + 3], tr1 = topLine[to + 3], tr2 = topLine[to + 3];
        if (topRightAvailable) {
            tr0 = topLine[to + 4];
            tr1 = topLine[to + 5];
            tr2 = topLine[to + 6];
        }

        int c0 = ((topLine[to] + topLine[to + 1] + 1) >> 1);
        int c1 = ((topLine[to + 1] + topLine[to + 2] + 1) >> 1);
        int c2 = ((topLine[to + 2] + topLine[to + 3] + 1) >> 1);
        int c3 = ((topLine[to + 3] + tr0 + 1) >> 1);
        int c4 = ((tr0 + tr1 + 1) >> 1);
        int c5 = ((topLine[to] + 2 * topLine[to + 1] + topLine[to + 2] + 2) >> 2);
        int c6 = ((topLine[to + 1] + 2 * topLine[to + 2] + topLine[to + 3] + 2) >> 2);
        int c7 = ((topLine[to + 2] + 2 * topLine[to + 3] + tr0 + 2) >> 2);
        int c8 = ((topLine[to + 3] + 2 * tr0 + tr1 + 2) >> 2);
        int c9 = ((tr0 + 2 * tr1 + tr2 + 2) >> 2);

        int off = (blkY << 4) + blkX;
        pixOut[off] = (byte) clip(residual[0] + c0, -128, 127);
        pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127);
        pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127);
        pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127);

        pixOut[off + 16] = (byte) clip(residual[4] + c5, -128, 127);
        pixOut[off + 17] = (byte) clip(residual[5] + c6, -128, 127);
        pixOut[off + 18] = (byte) clip(residual[6] + c7, -128, 127);
        pixOut[off + 19] = (byte) clip(residual[7] + c8, -128, 127);

        pixOut[off + 32] = (byte) clip(residual[8] + c1, -128, 127);
        pixOut[off + 33] = (byte) clip(residual[9] + c2, -128, 127);
        pixOut[off + 34] = (byte) clip(residual[10] + c3, -128, 127);
        pixOut[off + 35] = (byte) clip(residual[11] + c4, -128, 127);

        pixOut[off + 48] = (byte) clip(residual[12] + c6, -128, 127);
        pixOut[off + 49] = (byte) clip(residual[13] + c7, -128, 127);
        pixOut[off + 50] = (byte) clip(residual[14] + c8, -128, 127);
        pixOut[off + 51] = (byte) clip(residual[15] + c9, -128, 127);
    }

    public static void predictHorizontalUp(int[] residual, boolean leftAvailable, byte[] leftRow, int mbOffX, int blkX,
            int blkY, byte[] pixOut) {

        int c0 = ((leftRow[blkY] + leftRow[blkY + 1] + 1) >> 1);
        int c1 = ((leftRow[blkY] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2);
        int c2 = ((leftRow[blkY + 1] + leftRow[blkY + 2] + 1) >> 1);
        int c3 = ((leftRow[blkY + 1] + (leftRow[blkY + 2] << 1) + leftRow[blkY + 3] + 2) >> 2);
        int c4 = ((leftRow[blkY + 2] + leftRow[blkY + 3] + 1) >> 1);
        int c5 = ((leftRow[blkY + 2] + (leftRow[blkY + 3] << 1) + leftRow[blkY + 3] + 2) >> 2);
        int c6 = leftRow[blkY + 3];

        int off = (blkY << 4) + blkX;
        pixOut[off] = (byte) clip(residual[0] + c0, -128, 127);
        pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127);
        pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127);
        pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127);

        pixOut[off + 16] = (byte) clip(residual[4] + c2, -128, 127);
        pixOut[off + 17] = (byte) clip(residual[5] + c3, -128, 127);
        pixOut[off + 18] = (byte) clip(residual[6] + c4, -128, 127);
        pixOut[off + 19] = (byte) clip(residual[7] + c5, -128, 127);

        pixOut[off + 32] = (byte) clip(residual[8] + c4, -128, 127);
        pixOut[off + 33] = (byte) clip(residual[9] + c5, -128, 127);
        pixOut[off + 34] = (byte) clip(residual[10] + c6, -128, 127);
        pixOut[off + 35] = (byte) clip(residual[11] + c6, -128, 127);

        pixOut[off + 48] = (byte) clip(residual[12] + c6, -128, 127);
        pixOut[off + 49] = (byte) clip(residual[13] + c6, -128, 127);
        pixOut[off + 50] = (byte) clip(residual[14] + c6, -128, 127);
        pixOut[off + 51] = (byte) clip(residual[15] + c6, -128, 127);
    }
}